using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using Microsoft.Samples.BizTalk.Adapter.Tcp;

namespace TcpClient
{
    /// <summary>
    ///     This structure represents a data buffer.
    /// </summary>
    /// <author>
    ///     Paul Brown
    /// </author>
    public struct DataBuffer
    {
        public string               Data;
        public byte[]               Buffer;
        public int                  Counter;
        public DataEncodingTypes    DataEncoding;
    }

    /// <summary>
    ///     This class represents the main application form.
    /// </summary>
    public partial class Main : Form
    {
        #region Private Instance Members

        private bool            _isConnected;
        private Socket          _clientSocket;

        #endregion

        #region Private Instance Delegates

        // This delegate enables asynchronous calls for setting the text property of a control
        private delegate void SetTextCallback(string text);

        // This delegate enables asynchronous calls to get the value of a control
        private delegate object GetControlValueCallback(Control control);

        // This delegate enables asynchronous calls to set the state of the control
        private delegate void SetControlStateCallback(Control control, bool enabled);

        #endregion

        #region Constructor

        /// <summary>
        ///     This is the default constructor for the class.
        /// </summary>
        public Main()
        {
            InitializeComponent();

            // Initialize members
            this._isConnected = false;
            this._clientSocket = null;
        }

        #endregion

        #region Private Instance Methods

        /// <summary>
        ///     This method sets the enabled state of the buttons depending on whether it is connected.
        /// </summary>
        private void SetButtonState()
        {
            // Set button state based on whether connected
            this.SetControlState(this.buttonConnect, !this._isConnected);
            this.SetControlState(this.buttonDisconnect, this._isConnected);
            this.SetControlState(this.buttonSend, this._isConnected);
        }

        /// <summary>
        ///     This method sets the state of the control in a thread safe manner.
        /// </summary>
        private void SetControlState(Control control, bool enabled)
        {
            // If this property is true (calling thread id is different to creating thread id), we must invoke 
            if (control.InvokeRequired)
            {
                // On a different thread
                SetControlStateCallback callback = new SetControlStateCallback(this.SetControlState);
                this.Invoke(callback, control, enabled);
            }
            else
            {
                // On the same thread
                control.Enabled = enabled;
            }
        }

        /// <summary>
        ///     This method gets the control value in a thread safe manner.
        /// </summary>
        private object GetControlValue(Control control)
        {
            // If this property is true (calling thread id is different to creating thread id), we must invoke 
            if (control.InvokeRequired)
            {
                // On a different thread
                GetControlValueCallback callback = new GetControlValueCallback(this.GetControlValue);
                return (this.Invoke(callback, control));
            }
            else
            {
                // On the same thread
                if (control is ComboBox)
                {
                    return (((ComboBox)control).SelectedItem);
                }
                if (control is TextBox)
                {
                    return (((TextBox)control).Text);
                }
                if (control is CheckBox)
                {
                    return (((CheckBox)control).Checked);
                }
                if (control is RadioButton)
                {
                    return (((RadioButton)control).Checked);
                }
            }

            // Not a type we support
            return (null);
        }

        /// <summary>
        ///     This method sets the text of the control in a thread safe manner.
        /// </summary>
        private void SetReceiveDataText(string text)
        {
            // If this property is true (calling thread id is different to creating thread id), we must invoke 
            if (this.textReceiveData.InvokeRequired)
            {
                // On a different thread, we must invoke
                SetTextCallback callback = new SetTextCallback(this.SetReceiveDataText);
                this.Invoke(callback, text);
            }
            else
            {
                // On the same thread, we can just set it
                this.textReceiveData.Text = text;
            }
        }

        /// <summary>
        ///     This method sets the text of the control in a thread safe manner.
        /// </summary>
        private void SetStatusBarText(string text)
        {
            // If this property is true (calling thread id is different to creating thread id), we must invoke 
            if (this.toolStripContainer.InvokeRequired)
            {
                // On a different thread, we must invoke
                SetTextCallback callback = new SetTextCallback(this.SetStatusBarText);
                this.Invoke(callback, text);
            }
            else
            {
                // On the same thread, we can just set it
                this.labelStatus.Text = text;
            }
        }

        /// <summary>
        ///     This method returns a hex string in the form NN NN NN from the byte array.
        /// </summary>
        private string GetHexString(byte[] bytes, int offset, int count)
        {
            StringBuilder sb = new StringBuilder(count * 2);
            for (int i = offset; i < count; i++)
            {
                sb.AppendFormat("{0:X2} ", bytes[i]);
            }
            return (sb.ToString(0, sb.Length - 1));
        }

        /// <summary>
        ///     This method returns byte buffer as an appropriately encoded string.
        /// </summary>
        private string GetDecodedData(byte[] data, int offset, int count, DataEncodingTypes encoding, string customValue, bool bigEndian, bool handleByteOrderMark)
        {
            string decodedData = string.Empty;
            switch (encoding)
            {
                case DataEncodingTypes.ASCII:

                    decodedData = Encoding.ASCII.GetString(data, offset, count);
                    break;

                case DataEncodingTypes.Binary:

                    decodedData = this.GetHexString(data, offset, count);
                    break;

                case DataEncodingTypes.CustomIdentifier:

                    decodedData = Encoding.GetEncoding(int.Parse(customValue)).GetString(data, offset, count);
                    break;

                case DataEncodingTypes.CustomName:

                    decodedData = Encoding.GetEncoding(customValue).GetString(data, offset, count);
                    break;

                case DataEncodingTypes.UTF16:

                    Encoding utf16Encoding = new UnicodeEncoding(bigEndian, handleByteOrderMark, true);
                    decodedData = utf16Encoding.GetString(data, offset, count);
                    break;

                case DataEncodingTypes.UTF32:

                    Encoding utf32Encoding = new UTF32Encoding(bigEndian, handleByteOrderMark, true);
                    decodedData = utf32Encoding.GetString(data, offset, count);
                    break;

                case DataEncodingTypes.UTF7:

                    decodedData = Encoding.UTF7.GetString(data, offset, count);
                    break;

                case DataEncodingTypes.UTF8:

                    Encoding utf8Encoding = new UTF8Encoding(handleByteOrderMark, true);
                    decodedData = utf8Encoding.GetString(data, offset, count);
                    break;
            }

            // Returns the decoded data
            return (decodedData);
        }

        /// <summary>
        ///     This method returns the string converted into the appropriate encoding.
        /// </summary>
        private byte[] GetEncodedData(string data, DataEncodingTypes encoding, string customValue, bool bigEndian, bool emitByteOrderMark)
        {
            byte[] encodedBytes = null;
            switch (encoding)
            {
                case DataEncodingTypes.ASCII :

                    encodedBytes = Encoding.ASCII.GetBytes(data);
                    break;

                case DataEncodingTypes.Binary :

                    string[] bytes = data.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    encodedBytes = new byte[bytes.Length];
                    for (int i = 0; i < encodedBytes.Length; i++)
                    {
                        encodedBytes[i] = Convert.ToByte(bytes[i], 16);
                    }
                    break;

                case DataEncodingTypes.CustomIdentifier :

                    encodedBytes = Encoding.GetEncoding(int.Parse(customValue)).GetBytes(data);
                    break;

                case DataEncodingTypes.CustomName :

                    encodedBytes = Encoding.GetEncoding(customValue).GetBytes(data);
                    break;

                case DataEncodingTypes.UTF16 :

                    Encoding utf16Encoding = new UnicodeEncoding(bigEndian, emitByteOrderMark, true);
                    encodedBytes = utf16Encoding.GetBytes(data);
                    break;

                case DataEncodingTypes.UTF32 :

                    Encoding utf32Encoding = new UTF32Encoding(bigEndian, emitByteOrderMark, true);
                    encodedBytes = utf32Encoding.GetBytes(data);
                    break;

                case DataEncodingTypes.UTF7 :

                    encodedBytes = Encoding.UTF7.GetBytes(data);
                    break;

                case DataEncodingTypes.UTF8 :

                    Encoding utf8Encoding = new UTF8Encoding(emitByteOrderMark, true);
                    encodedBytes = utf8Encoding.GetBytes(data);
                    break;
            }

            // Returns the encoded bytes
            return (encodedBytes);
        }

        #endregion

        #region Private Event Handlers

        /// <summary>
        ///     This event handler is called when the form is loaded.
        /// </summary>
        private void Main_Load(object sender, EventArgs e)
        {
            // Default some of the values
            this.textIpAddress.Text = "localhost";
            this.textPort.Text = "1969";
            this.radioResponseTypeNone.Checked = true;
            this.SetButtonState();

            // Load encodings
            this.comboSendEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.ASCII);
            this.comboSendEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.CustomIdentifier);
            this.comboSendEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.CustomName);
            this.comboSendEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF16);
            this.comboSendEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF32);
            this.comboSendEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF7);
            this.comboSendEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF8);
            this.comboSendEncoding.SelectedIndex = 0;

            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.ASCII);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.CustomIdentifier);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.CustomName);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF16);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF32);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF7);
            this.comboReceiveEncoding.Items.Add(Microsoft.Samples.BizTalk.Adapter.Tcp.DataEncodingTypes.UTF8);
            this.comboReceiveEncoding.SelectedIndex = 0;
        }

        /// <summary>
        ///     This event handler is called when the connect button is clicked.
        /// </summary>
        private void buttonConnect_Click(object sender, EventArgs e)
        {
            this.Cursor = Cursors.WaitCursor;

            try
            {
                // Validate entries
                if (this.textIpAddress.Text == string.Empty) throw new ArgumentNullException("Please specify an IP Address");
                if (this.textPort.Text == string.Empty) throw new ArgumentNullException("Please specify a port");

                // Get port
                int port = int.Parse(this.textPort.Text);

                // Get remote endpoint
                IPHostEntry host = Dns.GetHostEntry(this.textIpAddress.Text);
                #region JPLUMMER IPV6 change

                //IPEndPoint remoteEndpoint = new IPEndPoint(host.AddressList[1], port);
                IPEndPoint remoteEndpoint = null; ;
                foreach (IPAddress addr in host.AddressList)
                {
                    if (addr.AddressFamily == AddressFamily.InterNetwork)
                    {
                        remoteEndpoint = new IPEndPoint(addr, port);
                        break;
                    }
                }
                

                
                #endregion
                // Create socket, bind and then connect
                this._clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPEndPoint localEndpoint = new IPEndPoint(IPAddress.Any, 0);
                this._clientSocket.Bind(localEndpoint);
                this._clientSocket.Connect(remoteEndpoint);

                // Set button state
                this._isConnected = true;
                this.SetButtonState();

                this.labelStatus.Text = "Connected";

                // If we are expecting a response (either duplex or normal response)
                if ((bool)this.GetControlValue(this.radioResponseTypeRequestResponse) ||
                    (bool)this.GetControlValue(this.radioResponseTypeDuplexResponse))
                {
                    // Receive data
                    DataBuffer recvBuffer = new DataBuffer();
                    recvBuffer.Buffer = new byte[2048];
                    recvBuffer.DataEncoding = (DataEncodingTypes)this.GetControlValue(this.comboReceiveEncoding);
                    this._clientSocket.BeginReceive(recvBuffer.Buffer, 0, recvBuffer.Buffer.Length, SocketFlags.None, new AsyncCallback(this.ReceiveCallback), recvBuffer);
                }
            }
            catch (Exception ex)
            {
                // Set button state
                this._clientSocket = null;
                this._isConnected = false;
                this.SetButtonState();

                this.labelStatus.Text = "Disconnected";

                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                this.Cursor = Cursors.Default;
            }
        }

        /// <summary>
        ///     This event handler is called when the disconnect button is clicked.
        /// </summary>
        private void buttonDisconnect_Click(object sender, EventArgs e)
        {
            try
            {
                // Are we connected?
                if (!this._isConnected)
                    return;

                // Disconnect
                if (this._clientSocket != null)
                {
                    // Close socket
                    this._clientSocket.Close();
                }

                // Set states
                this._isConnected = false;
                this.SetButtonState();

                this.labelStatus.Text = "Disconnected";
            }
            catch (Exception ex)
            {
                // Set button state
                this._clientSocket = null;
                this._isConnected = false;
                this.SetButtonState();

                this.labelStatus.Text = "Disconnected";

                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        ///     This event handler is called whenever the send button is clicked.
        /// </summary>
        private void buttonSend_Click(object sender, EventArgs e)
        {
            try
            {
                // Validate
                if (this.textSendData.Text == string.Empty) throw new ArgumentNullException("Enter some send data to be sent to the server");
                if (this.textSendFrameStart.Text == string.Empty) throw new ArgumentNullException("Enter the start frame delimiter");
                if (this.textSendFrameEnd.Text == string.Empty) throw new ArgumentNullException("Enter the end frame delimiter");

                // Get frame start and end bytes
                DataEncodingTypes frameStartEncoding = (DataEncodingTypes)this.comboSendEncoding.SelectedItem;
                if (this.checkSendBinaryFrame.Checked)
                {
                    frameStartEncoding = DataEncodingTypes.Binary;
                }
                byte[] frameStart = this.GetEncodedData(this.textSendFrameStart.Text, frameStartEncoding, this.textSendCustomEncoding.Text, this.checkSendByteOrder.Checked, this.checkSendEmitByteOrderMarker.Checked);

                DataEncodingTypes frameEndEncoding = (DataEncodingTypes)this.comboSendEncoding.SelectedItem;
                if (this.checkSendBinaryFrame.Checked)
                {
                    frameEndEncoding = DataEncodingTypes.Binary;
                }
                byte[] frameEnd = this.GetEncodedData(this.textSendFrameEnd.Text, frameEndEncoding, this.textSendCustomEncoding.Text, this.checkSendByteOrder.Checked, this.checkSendEmitByteOrderMarker.Checked);

                // Get data bytes
                byte[] data = this.GetEncodedData(this.textSendData.Text, (DataEncodingTypes)this.comboSendEncoding.SelectedItem, this.textSendCustomEncoding.Text, this.checkSendByteOrder.Checked, this.checkSendEmitByteOrderMarker.Checked);

                // Send the data provided we are connected
                if (this._isConnected)
                {
                    // Get bytes
                    byte[] buffer = new byte[frameStart.Length + data.Length + frameEnd.Length];
                    frameStart.CopyTo(buffer, 0);
                    data.CopyTo(buffer, frameStart.Length);
                    frameEnd.CopyTo(buffer, frameStart.Length + data.Length);

                    // Send data
                    int bytesSent = this._clientSocket.Send(buffer);
                    this.labelStatus.Text = "Sent " + bytesSent.ToString() + " bytes";
                }
            }
            catch (Exception ex)
            {
                // Set button state
                if (this._clientSocket != null)
                {
                    if (this._clientSocket.Connected)
                    {
                        this._clientSocket.Shutdown(SocketShutdown.Send);
                        this._clientSocket.Close();
                    }
                }
                this._clientSocket = null;
                this._isConnected = false;
                this.SetButtonState();

                this.labelStatus.Text = "Disconnected";

                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        ///     This event handler is called whenever the index is changed in the combo box.
        /// </summary>
        private void comboSendEncoding_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Check encoding
            DataEncodingTypes encoding = (DataEncodingTypes)this.comboSendEncoding.SelectedItem;
            if (encoding == DataEncodingTypes.UTF8 || encoding == DataEncodingTypes.UTF16 || encoding == DataEncodingTypes.UTF32)
            {
                this.labelSendCustomEncoding.Enabled = false;
                this.textSendCustomEncoding.Enabled = false;
                this.checkSendEmitByteOrderMarker.Enabled = true;
                if (encoding != DataEncodingTypes.UTF8)
                {
                    this.checkSendByteOrder.Enabled = true;
                }
                else
                {
                    this.checkSendByteOrder.Enabled = false;
                }
            }
            else if (encoding == DataEncodingTypes.CustomIdentifier || encoding == DataEncodingTypes.CustomName)
            {
                this.labelSendCustomEncoding.Enabled = true;
                this.textSendCustomEncoding.Enabled = true;
                this.checkSendEmitByteOrderMarker.Enabled = false;
                this.checkSendByteOrder.Enabled = false;
            }
            else
            {
                this.labelSendCustomEncoding.Enabled = false;
                this.textSendCustomEncoding.Enabled = false;
                this.checkSendEmitByteOrderMarker.Enabled = false;
                this.checkSendByteOrder.Enabled = false;
            }
        }

        /// <summary>
        ///     This event handler is called whenever the index is changed in the combo box.
        /// </summary>
        private void comboReceiveEncoding_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Check encoding
            DataEncodingTypes encoding = (DataEncodingTypes)this.comboReceiveEncoding.SelectedItem;
            if (encoding == DataEncodingTypes.UTF8 || encoding == DataEncodingTypes.UTF16 || encoding == DataEncodingTypes.UTF32)
            {
                this.labelReceiveCustomEncoding.Enabled = false;
                this.textReceiveCustomEncoding.Enabled = false;
                this.checkReceiveEmitByteOrderMarker.Enabled = true;
                if (encoding != DataEncodingTypes.UTF8)
                {
                    this.checkReceiveByteOrder.Enabled = true;
                }
                else
                {
                    this.checkReceiveByteOrder.Enabled = false;
                }
            }
            else if (encoding == DataEncodingTypes.CustomIdentifier || encoding == DataEncodingTypes.CustomName)
            {
                this.labelReceiveCustomEncoding.Enabled = true;
                this.textReceiveCustomEncoding.Enabled = true;
                this.checkReceiveEmitByteOrderMarker.Enabled = false;
                this.checkReceiveByteOrder.Enabled = false;
            }
            else
            {
                this.labelReceiveCustomEncoding.Enabled = false;
                this.textReceiveCustomEncoding.Enabled = false;
                this.checkReceiveEmitByteOrderMarker.Enabled = false;
                this.checkReceiveByteOrder.Enabled = false;
            }
        }

        /// <summary>
        ///     This asynchronous handler is called whenever data is waiting to be received.
        /// </summary>
        private void ReceiveCallback(IAsyncResult asyncResult)
        {
            try
            {
                // Get counter
                DataBuffer buffer = (DataBuffer)asyncResult.AsyncState;

                // End receive
                int recvSize = this._clientSocket.EndReceive(asyncResult);
                buffer.Counter += recvSize;

                if (recvSize > 0)
                {
                    // Get decoded data and set textbox value
                    buffer.Data += this.GetDecodedData(buffer.Buffer, 0, recvSize, buffer.DataEncoding, (string)this.GetControlValue(this.textReceiveCustomEncoding), (bool)this.GetControlValue(this.checkReceiveByteOrder), (bool)this.GetControlValue(this.checkReceiveEmitByteOrderMarker));
                    this.SetReceiveDataText(buffer.Data);
                    this.SetStatusBarText("Data received, total bytes = " + buffer.Counter.ToString());
                }
                else
                {
                    // No more data - no need to close socket as this is a duplex connection
                    this.SetStatusBarText("All data received, total bytes = " + buffer.Counter.ToString());
                }

                // Receive again
                this._clientSocket.BeginReceive(buffer.Buffer, 0, buffer.Buffer.Length, SocketFlags.None, new AsyncCallback(this.ReceiveCallback), buffer);
            }
            catch (ObjectDisposedException)
            {
                // Socket closed while this delegate is still running
            }
            catch (Exception ex)
            {
                this.SetButtonState();

                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        #endregion
    }
}